package com.anuyun.ms.base.aspect;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.anuyun.ms.base.annotation.Log;
import com.anuyun.ms.base.common.BusinessException;
import com.anuyun.ms.base.constant.TimeConstant;
import com.anuyun.ms.base.util.DateUtil;
import com.anuyun.ms.base.util.IdUtil;

@Aspect
@Component
public class LogAspect {
	private final Logger log = LoggerFactory.getLogger(this.getClass());

	/**
	 * 记录日志id
	 */
	private String id;

	/**
	 * 日志的操作说明
	 */
	private String description;


	/**
	 * 日志开始时间
	 */
	private LocalDateTime beginTime;

	/**
	 * 日志结束时间
	 */
	private LocalDateTime endTime;

	/**
	 * 注解切点
	 */
	@Pointcut("@annotation(com.anuyun.ms.base.annotation.Log)")
	public void point() {
	}

	/**
	 * 前置通知
	 * 
	 * @param joinPoint
	 */
	@Before("point()")
	public void doBefore(JoinPoint joinPoint) {

		this.id = IdUtil.createUUID();
		this.beginTime = LocalDateTime.now();

		List<String> parameterTypes = this.getLogAnnotationParm(joinPoint);
		String targetName = joinPoint.getSignature().getDeclaringTypeName();
		String methodName = joinPoint.getSignature().getName();

		String args = JSON.toJSONString(joinPoint.getArgs());

		log.info("--------begin: {}--------", DateUtil.toString(beginTime,TimeConstant.TIME_FORMAT_MS));
		log.info("当前操作: " + this.description + "---操作ID: " + this.id);
		log.info("调用方法: " + targetName + "." + methodName + "(" + parameterTypes.toString() + ")");
		log.info("传入参数: " + args);
	}

	/**
	 * 环绕通知
	 * 
	 * @param pJoinPoint
	 * @return
	 */
//	@Around("point()")
//	public Object serviceAOP(ProceedingJoinPoint pJoinPoint) {
//
//		try {
//			return pJoinPoint.proceed();
//		} catch (Throwable e) {
//			if (this.handleException) {
//				if (e instanceof BusinessException) {
//					log.info("业务异常: " + e.getMessage());
//					return new Result<>(((BusinessException) e).getCode(), e.getMessage());
//				} else {
//					log.error("系统异常: ", e);
//					return new Result<>(1, "系统错误: " + e.getMessage());
//				}
//			} else {
//				throw new RuntimeException(e);
//			}
//		}
//
//	}

	/**
	 * 返回通知
	 * 
	 * @param joinPoint
	 * @param rvt
	 * @return
	 */
	@AfterReturning(pointcut = "point()", returning = "obj")
	public Object AfterExec(JoinPoint joinPoint, Object obj) {
		this.endTime = LocalDateTime.now();
		
		log.info("返回参数: " + JSON.toJSONString(obj));
		log.info("耗时时间: {} ms" , DateUtil.differMilliSeconds(this.beginTime,this.endTime));
		log.info("--------e n d: {}--------", DateUtil.toString(endTime,TimeConstant.TIME_FORMAT_MS));
		return obj;
	}

	/**
	 * 异常通知 handleException为false时生效
	 * 
	 * @param joinPoint
	 * @param e
	 */
	@AfterThrowing(pointcut = "point()", throwing = "e")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
		
		if (e instanceof BusinessException) {
			log.info("业务异常: [warn]" + e.getMessage());
		} else {
			log.error("系统异常: ", e);
		}
		this.endTime = LocalDateTime.now();
		log.info("耗时时间: {} ms" , DateUtil.differMilliSeconds(this.beginTime,this.endTime));
		log.info("--------e n d: {}--------", DateUtil.toString(endTime,TimeConstant.TIME_FORMAT_MS));

	}

	/**
	 * 获取注解中对方法的描述信息 用于Controller层注解
	 *
	 */
	@SuppressWarnings("rawtypes")
	public List<String> getLogAnnotationParm(JoinPoint joinPoint) {
		String targetName = joinPoint.getSignature().getDeclaringTypeName();
		String methodName = joinPoint.getSignature().getName();

		Object[] arguments = joinPoint.getArgs();
		Class targetClass = null;
		try {
			targetClass = Class.forName(targetName);
		} catch (ClassNotFoundException e) {
			log.info(targetName + "找不到，无法实例化");
		}
		Method[] methods = targetClass.getMethods();
		Class[] clazzs = null;
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				clazzs = method.getParameterTypes();
				if (clazzs.length == arguments.length) {
					this.description = method.getAnnotation(Log.class).value();
					break;
				}
			}
		}

		List<String> parameterTypes = new ArrayList<>();
		if (clazzs != null) {
			for (Class c : clazzs) {
				parameterTypes.add(c.getSimpleName());
			}
		}

		return parameterTypes;
	}

}
